﻿namespace DCHealthCheck.Common {

    #region Usings
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Diagnostics;
    using System.Data.SqlClient;
    using System.ComponentModel;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.DirectoryServices.Protocols;
    using System.IO;
    using System.IO.Compression;
    #endregion

    public static class Extensions {

        private static readonly DateTime sqlDateTimeMinValue = new DateTime(1753, 1, 1);

        [DebuggerStepThroughAttribute]
        public static string CurrentMethodName() {
            StackFrame frame = new StackFrame(1);
            var method = frame.GetMethod();
            var type = method.DeclaringType;
            var name = method.Name;

            return type + "::" + name + "(): ";
        }

        [DebuggerStepThroughAttribute]
        public static void Each<T>(this IEnumerable<T> items, Action<T> work) {
            foreach (T item in items)
                work(item);
        }

        public static TEntity GetItemObject<TEntity>(object dataItem) where TEntity : class {
            var entity = dataItem as TEntity;

            if (entity != null) {

                return entity;

            }

            var typeDescriptor = dataItem as ICustomTypeDescriptor;

            if (typeDescriptor != null) {
                return typeDescriptor.GetPropertyOwner(null) as TEntity;
            }

            return null;
        }

        [DebuggerStepThroughAttribute]
        public static string LDAPSSLFriendlyInformation(this SecurityPackageContextConnectionInformation contextInfo) {
            var info = new StringBuilder();

            info.Append(string.Format("AlgorithmIdentifier: {0}; ", contextInfo.AlgorithmIdentifier));
            info.Append(string.Format("CipherStrength: {0}; ", contextInfo.CipherStrength));
            info.Append(string.Format("ExchangeStrength: {0}; ", contextInfo.ExchangeStrength));
            info.Append(string.Format("Hash: {0}; ", contextInfo.Hash));
            info.Append(string.Format("HashStrength: {0}; ", contextInfo.HashStrength));
            info.Append(string.Format("KeyExchangeAlgorithm: {0}; ", contextInfo.KeyExchangeAlgorithm));
            info.Append(string.Format("Protocol: {0}; ", contextInfo.Protocol));

            return info.ToString();
        }

        /// <summary>
        /// If a name has dots/is a fully-qualified name, return the first name part
        /// </summary>
        [DebuggerStepThroughAttribute]
        public static string SingleLabelName(this string name) {
            int firstDotIndex = name.IndexOf(".");
            if (firstDotIndex > -1) {
                return name.Substring(0, firstDotIndex);
            }
            else {
                return name;
            }
        }

        /// <summary>
        /// Helper method to trim string values of custom objects
        /// </summary>
        /// <param name="customObject"></param>
        [DebuggerStepThroughAttribute]
        public static void TrimstringValues(this object customObject) {
            PropertyInfo[] properties = customObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);

            foreach (PropertyInfo propertyInfo in properties) {
                // Only work with strings
                if (propertyInfo.PropertyType != typeof(string)) { continue; }

                // If not writable then cannot null it; if not readable then cannot check it's value
                if (!propertyInfo.CanWrite || !propertyInfo.CanRead) { continue; }

                MethodInfo getMethod = propertyInfo.GetGetMethod(false);
                MethodInfo setMethod = propertyInfo.GetSetMethod(false);

                // Get and set methods have to be public
                if (getMethod == null) { continue; }
                if (setMethod == null) { continue; }

                string currentValue = propertyInfo.GetValue(customObject, new string[] { }) as string;
                if (!string.IsNullOrEmpty(currentValue)) {
                    if (currentValue.StartsWith(" ") || currentValue.EndsWith(" ")) {
                        propertyInfo.SetValue(customObject, currentValue.Trim(), new object[] { });
                    }
                }
            }
        }

        #region Byte array
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times"), DebuggerStepThroughAttribute]
        public static string FromCompressedByteArray(this byte[] compressedData) {
            string decompressedText = string.Empty;

            using (MemoryStream memoryStreamInput = new MemoryStream(compressedData))
            using (MemoryStream memoryStreamOutput = new MemoryStream()) {
                using (DeflateStream deflateStream = new DeflateStream(memoryStreamInput, CompressionMode.Decompress, true)) {
                    deflateStream.CopyTo(memoryStreamOutput);
                }

                decompressedText = Encoding.Unicode.GetString(memoryStreamOutput.ToArray());
            }

            return decompressedText;
        }

        /// <summary>
        /// Converts the string into a Byte Array using Unicode
        /// </summary>
        /// <param name="str">string to Convert</param>
        /// <returns>Byte Array</returns>
        [DebuggerStepThroughAttribute]
        public static byte[] ToByteArray(this string str) { return ToByteArray(str, System.Text.UnicodeEncoding.Unicode); }

        /// <summary>
        /// Converts the string into a Byte Array
        /// </summary>
        /// <param name="str">string to Convert</param>
        /// <returns>Byte Array</returns>
        [DebuggerStepThroughAttribute]
        public static byte[] ToByteArray(this string str, System.Text.Encoding Encoding) {
            return Encoding.GetBytes(str);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times"), DebuggerStepThroughAttribute]
        public static byte[] ToCompressedByteArray(this string stringToCompress) {
            byte[] compressedData = null;
            byte[] uncompressedData = Encoding.Unicode.GetBytes(stringToCompress);

            using (MemoryStream compressedMemoryStream = new MemoryStream()) {
                using (DeflateStream deflateStream = new DeflateStream(compressedMemoryStream, CompressionMode.Compress, true)) {
                    deflateStream.Write(uncompressedData, 0, uncompressedData.Length);
                }

                compressedMemoryStream.Position = 0;
                compressedData = compressedMemoryStream.ToArray();
            }

            return compressedData;
        }
        #endregion

        #region Exceptions
        [DebuggerStepThroughAttribute]
        public static string LogSqlErrors(SqlException e) {
            var error = new StringBuilder();
            for (int index = 0; index < e.Errors.Count; index++) {
                error.AppendLine();
                error.AppendLine("SqlException:");
                error.AppendLine(string.Format("Line Number: {0}", e.Errors[index].LineNumber));
                error.AppendLine(string.Format("Message: {0}", e.Errors[index].Message));
                error.AppendLine(string.Format("Error Number: {0}", e.Errors[index].Number));
                error.AppendLine(string.Format("Procedure: {0}", e.Errors[index].Procedure));
                error.AppendLine(string.Format("Server: {0}", e.Errors[index].Server));
                error.AppendLine(string.Format("Source: {0}", e.Errors[index].Source));
            }

            return error.ToString();
        }

        /// <summary>
        /// Stack trace, target site, and error message of outer and inner exception, formatted with newlines
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="exception"></param>
        /// <returns></returns>
        [DebuggerStepThroughAttribute]
        public static string VerboseExceptionString<T>(this T exception) where T : Exception {
            var exceptionstring = new StringBuilder();

            exceptionstring.AppendLine(string.Format(" Exception: {0} Message: {1}", exception.GetType().Name, exception.Message != null ? exception.Message : "NULL"));
            exceptionstring.AppendLine(string.Format(" StackTrace: {0}", exception.StackTrace != null ? exception.StackTrace : "NULL"));
            exceptionstring.AppendLine(string.Format(" TargetSite: {0}", exception.TargetSite != null ? exception.TargetSite.ToString() : "NULL"));

            if (exception as SqlException != null) {
                exceptionstring.AppendLine(LogSqlErrors(exception as SqlException));
            }

            if (exception.InnerException != null) {
                exceptionstring.AppendLine();
                exceptionstring.AppendLine("Inner Exception:");
                exceptionstring.AppendLine(exception.InnerException.VerboseExceptionString());
            }

            return exceptionstring.ToString();
        }
        #endregion

        #region String collections
        [DebuggerStepThroughAttribute]
        public static string ToFormattedString(this List<string> list) {
            if ((list == null) || (list.Count == 0)) return string.Empty;
            return list.ToArray().ToFormattedString();
        }

        [DebuggerStepThroughAttribute]
        public static string ToFormattedString(this string[] list) {
            if ((list == null) || (list.Length == 0)) return string.Empty;
            return string.Join(Environment.NewLine, Array.ConvertAll<string, string>(list, new Converter<string, string>(Convert.ToString)));
        }

        [DebuggerStepThroughAttribute]
        public static string ToDelimitedString(this List<string> list) {
            if ((list == null) || (list.Count == 0)) return string.Empty;
            return list.ToArray().ToDelimitedString();
        }

        [DebuggerStepThroughAttribute]
        public static string ToDelimitedString(this string[] list) {
            if ((list == null) || (list.Length == 0)) return string.Empty;
            return string.Join("; ", Array.ConvertAll<string, string>(list, new Converter<string, string>(Convert.ToString)));
        }
        #endregion

        #region Linq OrderBy
        [DebuggerStepThroughAttribute]
        private static IOrderedQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName) {
            string[] props = property.Split('.');
            Type type = typeof(T);
            ParameterExpression arg = Expression.Parameter(type, "x");
            Expression expr = arg;
            foreach (string prop in props) {
                // use reflection (not ComponentModel) to mirror LINQ
                PropertyInfo pi = type.GetProperty(prop);
                expr = Expression.Property(expr, pi);
                type = pi.PropertyType;
            }
            Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
            LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);

            object result = typeof(Queryable).GetMethods().Single(
                    method => method.Name == methodName
                            && method.IsGenericMethodDefinition
                            && method.GetGenericArguments().Length == 2
                            && method.GetParameters().Length == 2)
                    .MakeGenericMethod(typeof(T), type)
                    .Invoke(null, new object[] { source, lambda });
            return (IOrderedQueryable<T>)result;
        }
        /// <summary>
        /// Generic method to sort a list by string column name, and direction
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        [DebuggerStepThroughAttribute]
        public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> source, string propertyName) {
            var expressionParameter = Expression.Parameter(typeof(T), "item");
            var sortExpression = Expression.Lambda<Func<T, object>>(
                Expression.Convert(Expression.Property(expressionParameter, propertyName), typeof(object)), expressionParameter);
            return source.AsQueryable<T>().OrderBy<T, object>(sortExpression);
        }
        [DebuggerStepThroughAttribute]
        public static IEnumerable<T> OrderByDescending<T>(this IEnumerable<T> source, string propertyName) {
            var expressionParameter = Expression.Parameter(typeof(T), "item");
            var sortExpression = Expression.Lambda<Func<T, object>>(
                Expression.Convert(Expression.Property(expressionParameter, propertyName), typeof(object)), expressionParameter);
            return source.AsQueryable<T>().OrderByDescending<T, object>(sortExpression);
        }
        [DebuggerStepThroughAttribute]
        public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string property) {
            return ApplyOrder<T>(source, property, "OrderBy");
        }
        [DebuggerStepThroughAttribute]
        public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string property) {
            return ApplyOrder<T>(source, property, "OrderByDescending");
        }
        [DebuggerStepThroughAttribute]
        public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string property) {
            return ApplyOrder<T>(source, property, "ThenBy");
        }
        [DebuggerStepThroughAttribute]
        public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string property) {
            return ApplyOrder<T>(source, property, "ThenByDescending");
        }
        #endregion      

        #region LinqToObjects AsHierarchy
        /// <summary>
        /// Recursively flattens a collection of HierarchyNodes
        /// </summary>
        /// <typeparam name="T">The type of nodes</typeparam>
        /// <param name="hierarchyList">The collection to flatten</param>
        /// <returns>The list of flattended nodes</returns>
        public static IEnumerable<HierarchyNode<T>> AsFlattenedHierarchy<T>(this IEnumerable<HierarchyNode<T>> hierarchyList) where T : class {
            foreach (var node in hierarchyList) {
                yield return node;
                foreach (var childNode in node.ChildNodes.AsFlattenedHierarchy()) {
                    yield return childNode;
                }
            }
        }

        /// <summary>
        /// Turns a flat list of items into a hierarchical data structure, then returns a flattened projection.
        /// </summary>
        /// <typeparam name="T">Type of the object</typeparam>
        /// <typeparam name="TProperty">The type of the Id property on the object</typeparam>
        /// <param name="allItems">Items to place in the hierarchy</param>
        /// <param name="idProperty">Used to get the objects Id</param>
        /// <param name="parentIdProperty">Used to get the objects parent Id</param>
        /// <returns>A collection of flattened HierarchyNode objects</returns>
        public static IEnumerable<HierarchyNode<T>> AsFlattenedHierarchy<T, TProperty>(this IEnumerable<T> allItems, Func<T, TProperty> idProperty, Func<T, TProperty> parentIdProperty) where T : class {
            return AsHierarchy(allItems, idProperty, parentIdProperty).AsFlattenedHierarchy();
        }

        /// <summary>
        /// Turns a flat list of items into a hierarchical data structure.  The root
        /// levels will be any object that have no value set for parentIdProperty. 
        /// </summary>
        /// <typeparam name="T">Type of the object</typeparam>
        /// <typeparam name="TProperty">The type of the Id property on the object</typeparam>
        /// <param name="allItems">Items to place in the hierarchy</param>
        /// <param name="idProperty">Used to get the objects Id</param>
        /// <param name="parentIdProperty">Used to get the objects parent Id</param>
        /// <returns>A collection of HierarchyNode objects</returns>
        public static IEnumerable<HierarchyNode<T>> AsHierarchy<T, TProperty>(this IEnumerable<T> allItems, Func<T, TProperty> idProperty, Func<T, TProperty> parentIdProperty) {
            HierarchySearcher<T, TProperty> searcher = new HierarchySearcher<T, TProperty>(allItems, idProperty, parentIdProperty);

            return searcher
                    .GetAllRoots()
                    .Select(o => CreateHierarchy(searcher, o));
        }

        public static HierarchyNode<T> AsHierarchy<T, TProperty>(this IEnumerable<T> allItems, T parent) where T : IHierarchical<TProperty> {
            Func<T, TProperty> idProperty = item => item.ItemId;
            Func<T, TProperty> parentIdProperty = item => item.ParentId;

            return AsHierarchy(allItems, parent, idProperty, parentIdProperty);
        }

        /// <summary>
        /// Returns a hierarchical data structure with the parent item as the root.
        /// </summary>
        /// <typeparam name="T">Type of the object</typeparam>
        /// <typeparam name="TProperty">The type of the Id property on the object</typeparam>
        /// <param name="allItems">Items to place in the hierarchy</param>
        /// <param name="idProperty">Used to get the objects Id</param>
        /// <param name="parentIdProperty">Used to get the objects parent Id</param>
        /// <returns></returns>
        public static HierarchyNode<T> AsHierarchy<T, TProperty>(this IEnumerable<T> allItems, T parent, Func<T, TProperty> idProperty, Func<T, TProperty> parentIdProperty) {
            HierarchySearcher<T, TProperty> searcher = new HierarchySearcher<T, TProperty>(allItems, idProperty, parentIdProperty);

            return CreateHierarchy(searcher, parent);
        }

        private static HierarchyNode<T> CreateHierarchy<T, TProperty>(HierarchySearcher<T, TProperty> searcher, T parent, int depth = -1) {
            depth++;

            IEnumerable<HierarchyNode<T>> children =
                searcher
                    .GetChildren(parent)
                    .Select(child => CreateHierarchy(searcher, child, depth));

            return new HierarchyNode<T>(parent, children, depth);
        }     
        #endregion

        #region DateTime
        /// <summary>
        /// "yyyy-MMM-dd HH:mm"
        /// </summary>
        [DebuggerStepThroughAttribute]
        public static string LongDateFriendly(this DateTime dateTime) {
            return dateTime == sqlDateTimeMinValue ? "N/A" : dateTime.ToString("yyyy-MMM-dd HH:mm");
        }

        /// <summary>
        /// "yyyy-MM-ddTHH:mm:ss.FFFFFFF"
        /// </summary>
        /// <remarks>
        /// Uses .FFFFFFF precision for SQL queries compatible with the DateTime2 type
        /// Uses numeric month for automatic parsing with DateTime.Parse()
        /// </remarks>
        [DebuggerStepThroughAttribute]
        public static string ParseableLongDateTimeSqlFriendly(this DateTime dateTime) {
            return dateTime.ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFF");
        }

        /// <summary>
        /// "yyyy-MMM-dd"
        /// </summary>
        [DebuggerStepThroughAttribute]
        public static string ShortDateFriendly(this DateTime dateTime) {
            return dateTime == sqlDateTimeMinValue ? "N/A" : dateTime.ToString("yyyy-MMM-dd");
        }

        public static string ToRfc2068Datestring(this DateTime datetime) {
            return datetime.ToString("ddd, dd MMM yyyy hh:mm:ss") + " GMT";
        }
        #endregion

        #region TimeSpan
        /// <summary>
        /// DD.HH:mm:ss
        /// </summary>
        [DebuggerStepThroughAttribute]
        public static string DHMSFriendly(this TimeSpan timeSpan) {
            return string.Format("{0:00}.{1:00}:{2:00}:{3:00}", timeSpan.Days, timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds);
        }
        /// <summary>
        /// HH:mm:ss
        /// </summary>
        [DebuggerStepThroughAttribute]
        public static string HMSFriendly(this TimeSpan timeSpan) {
            return string.Format("{0:00}:{1:00}:{2:00}", timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds);
        }
        /// <summary>
        /// HH:mm
        /// </summary>
        [DebuggerStepThroughAttribute]
        public static string HMFriendly(this TimeSpan timeSpan) {
            return string.Format("{0:00}:{1:00}", timeSpan.Hours, timeSpan.Minutes);
        }
        #endregion

    }
}
